Data download dei dati: 10-12-2024
Data ultimo upgrade dei dati: 22-11-2024
Sito origine dei dati: data.wa.gov
Link origine dei dati: https://catalog.data.gov/dataset/electric-vehicle-population-data
import polars as pl
from pathlib import Path
data_dir = Path('DATA')
data_file = ".."/ data_dir / "Electric_Vehicle_Population_Data.csv"
data = pl.read_csv(data_file)
#Breve visualizzazione dei dati
data.head()
data.describe()
| statistic | VIN (1-10) | County | City | State | Postal Code | Model Year | Make | Model | Electric Vehicle Type | Clean Alternative Fuel Vehicle (CAFV) Eligibility | Electric Range | Base MSRP | Legislative District | DOL Vehicle ID | Vehicle Location | Electric Utility | 2020 Census Tract |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| str | str | str | str | str | f64 | f64 | str | str | str | str | f64 | f64 | f64 | f64 | str | str | f64 |
| "count" | "216772" | "216767" | "216767" | "216772" | 216767.0 | 216772.0 | "216772" | "216772" | "216772" | "216772" | 216753.0 | 216753.0 | 216321.0 | 216772.0 | "216761" | "216767" | 216767.0 |
| "null_count" | "0" | "5" | "5" | "0" | 5.0 | 0.0 | "0" | "0" | "0" | "0" | 19.0 | 19.0 | 451.0 | 0.0 | "11" | "5" | 5.0 |
| "mean" | null | null | null | null | 98179.750714 | 2021.129039 | null | null | null | null | 49.428386 | 870.987045 | 28.920114 | 2.3045e8 | null | null | 5.2982e10 |
| "std" | null | null | null | null | 2458.320323 | 2.983918 | null | null | null | null | 86.224511 | 7544.671592 | 14.907934 | 7.0450e7 | null | null | 1.5147e9 |
| "min" | "1C4JJXN60P" | "Ada" | "Aberdeen" | "AE" | 1731.0 | 1999.0 | "ACURA" | "330E" | "Battery Electric Vehicle (BEV)" | "Clean Alternative Fuel Vehicle… | 0.0 | 0.0 | 1.0 | 4385.0 | "POINT (-100.50078 31.4168)" | "AVISTA CORP" | 1.0010e9 |
| "25%" | null | null | null | null | 98052.0 | 2020.0 | null | null | null | null | 0.0 | 0.0 | 17.0 | 1.96232816e8 | null | null | 5.3033e10 |
| "50%" | null | null | null | null | 98125.0 | 2022.0 | null | null | null | null | 0.0 | 0.0 | 32.0 | 2.44032309e8 | null | null | 5.3033e10 |
| "75%" | null | null | null | null | 98374.0 | 2023.0 | null | null | null | null | 42.0 | 0.0 | 42.0 | 2.64906967e8 | null | null | 5.3053e10 |
| "max" | "ZHWUC1ZM1R" | "Yuba" | "Zillah" | "WY" | 99577.0 | 2025.0 | "WHEEGO ELECTRIC CARS" | "ZDX" | "Plug-in Hybrid Electric Vehicl… | "Not eligible due to low batter… | 337.0 | 845000.0 | 49.0 | 4.79254772e8 | "POINT (-98.72277 29.44539)" | "PUGET SOUND ENERGY INC||PUD NO… | 5.6021e10 |
A seguito di un'analisi preliminare si nota che alcune colonne non risultano utili per l'analisi.
-VIN (1-10) -Postal Code -Base MSRP -Legislative District -DOL Vehicle ID -Electric Utility -2020 Census Tract -Clean Alternative Fuel Vehicle (CAFV) Eligibility
La descrizione è in lingua inglese perchè è la descrizione data dal sito: data.gov
Nota Bene I dato arrivano al 2025 a causa di alcuni preordini.
data = data.select(pl.exclude(['VIN (1-10)','Postal Code','Legislative District','DOL Vehicle ID','Electric Utility','2020 Census Tract','Clean Alternative Fuel Vehicle (CAFV) Eligibility']))
data.describe()
| statistic | County | City | State | Model Year | Make | Model | Electric Vehicle Type | Electric Range | Base MSRP | Vehicle Location |
|---|---|---|---|---|---|---|---|---|---|---|
| str | str | str | str | f64 | str | str | str | f64 | f64 | str |
| "count" | "216767" | "216767" | "216772" | 216772.0 | "216772" | "216772" | "216772" | 216753.0 | 216753.0 | "216761" |
| "null_count" | "5" | "5" | "0" | 0.0 | "0" | "0" | "0" | 19.0 | 19.0 | "11" |
| "mean" | null | null | null | 2021.129039 | null | null | null | 49.428386 | 870.987045 | null |
| "std" | null | null | null | 2.983918 | null | null | null | 86.224511 | 7544.671592 | null |
| "min" | "Ada" | "Aberdeen" | "AE" | 1999.0 | "ACURA" | "330E" | "Battery Electric Vehicle (BEV)" | 0.0 | 0.0 | "POINT (-100.50078 31.4168)" |
| "25%" | null | null | null | 2020.0 | null | null | null | 0.0 | 0.0 | null |
| "50%" | null | null | null | 2022.0 | null | null | null | 0.0 | 0.0 | null |
| "75%" | null | null | null | 2023.0 | null | null | null | 42.0 | 0.0 | null |
| "max" | "Yuba" | "Zillah" | "WY" | 2025.0 | "WHEEGO ELECTRIC CARS" | "ZDX" | "Plug-in Hybrid Electric Vehicl… | 337.0 | 845000.0 | "POINT (-98.72277 29.44539)" |
data = data.drop_nulls()
Cambiamo la denominazione della colonna Electric Vehicle Type, per semplificare la visualizzazione delle colonna.
data = data.with_columns(
pl.col('Electric Vehicle Type').replace({
'Plug-in Hybrid Electric Vehicle (PHEV)': 'PHEV',
'Battery Electric Vehicle (BEV)': 'BEV'
})
)
Gli obiettivi sono quelli di eseguire un'analisi completa sul dataset cercando di mostrare graficamente le informazioni che riguardano la vendita di auto elettriche, in base a determinate categorie.
Per semplicità quando si analizzano entrambe le tipologie di vetture verrà usato il termine 'auto', invece se si fa riferimento solo ad una categoria delle due verranno usati i termini 'BEV' o 'PHEV'.
(
data
.group_by('Model Year')
.agg(
tot_per_year = pl.col('Model Year').count()
)
.sort(pl.col('Model Year'))
)
| Model Year | tot_per_year |
|---|---|
| i64 | u32 |
| 1999 | 2 |
| 2000 | 7 |
| 2002 | 2 |
| 2003 | 1 |
| 2008 | 23 |
| … | … |
| 2021 | 20074 |
| 2022 | 28592 |
| 2023 | 60292 |
| 2024 | 40102 |
| 2025 | 1428 |
Si nota come nei primi anni il numero di auto vendute è molto basso, quindi per semplicità si prenderanno i dati a partire dal 2011.
data = (
data
.filter(pl.col('Model Year')>2010)
)
# salvataggio dei dati nel file data.csv
data.write_csv( ".."/ data_dir / "data.csv")
(
data
.group_by('Make')
.agg(
Vendita_per_marca = pl.col('Make').count()
)
.sort('Vendita_per_marca', descending = True)
)
| Make | Vendita_per_marca |
|---|---|
| str | u32 |
| "TESLA" | 93883 |
| "CHEVROLET" | 15862 |
| "NISSAN" | 15011 |
| "FORD" | 11477 |
| "KIA" | 10089 |
| … | … |
| "TH!NK" | 5 |
| "AZURE DYNAMICS" | 4 |
| "ROLLS-ROYCE" | 3 |
| "RAM" | 2 |
| "VINFAST" | 1 |
Si nota che alcuni marchi hanno un numero di vendite quasi pari a 0, quindi guardo la percentuale totale di auto vendute per ogni produttore.
Si vuole cercare un range tale per cui si andrà a non considerare un insieme di produttori. Creiamo un grafico a linee con sfumature di colore, per vedere quali sono i produttori che ha più senso eliminare.
Per semplicità di analisi grafica, si vedranno solo le auto che hanno una percentuale di auto vendute nel dataset inferiore al 0.5%.
import altair as alt
data_test = (
data
.group_by('Make')
.agg(
Vendita_per_marca = (pl.col('Make').count() / len(data)*100).round(3)
)
.filter(pl.col('Vendita_per_marca') < .5)
)
base = (
alt.Chart(data_test)
.encode(
x='Vendita_per_marca:Q',
y=alt.Y('Make:N', sort='-x'),
color=alt.Color('Vendita_per_marca:Q', scale=alt.Scale(scheme='oranges'))
)
)
base.mark_bar()
Dal grafico notiamo come ci sia una differenza signficativa tra un gruppo di auto e un altro. Quindi scelgo come soglia di esclusione dall'analisi per marca il numero 0.25%.
(
data
.group_by('Make')
.agg(
Vendita_per_marca = (pl.col('Make').count() / len(data)*100).round(3)
)
.filter(pl.col('Vendita_per_marca') < .25)
.sort('Vendita_per_marca', descending = True)
)
| Make | Vendita_per_marca |
|---|---|
| str | f64 |
| "LINCOLN" | 0.148 |
| "LUCID" | 0.146 |
| "GENESIS" | 0.137 |
| "SMART" | 0.113 |
| "JAGUAR" | 0.109 |
| … | … |
| "AZURE DYNAMICS" | 0.002 |
| "TH!NK" | 0.002 |
| "RAM" | 0.001 |
| "ROLLS-ROYCE" | 0.001 |
| "VINFAST" | 0.0 |
Total_County = (
data
.group_by('County')
.agg(
Total = pl.col('County').count()
)
.sort('Total', descending= True)
)
Total_County
| County | Total |
|---|---|
| str | u32 |
| "King" | 110122 |
| "Snohomish" | 26195 |
| "Pierce" | 17392 |
| "Clark" | 12937 |
| "Thurston" | 7936 |
| … | … |
| "Brevard" | 1 |
| "Salt Lake" | 1 |
| "James City" | 1 |
| "Wasco" | 1 |
| "Currituck" | 1 |
Risulta utile verificare la percentuale di auto immatricolate in base alla contea, così da avere un'idea più chiara di come si distribuiscono le auto rispetto al territorio.
num_car = len(data)
perc_county = (
data
.group_by('County')
.agg(
Total = (pl.col('County').count()/num_car*100).round(3)
)
.sort('Total', descending= True)
)
perc_county
| County | Total |
|---|---|
| str | f64 |
| "King" | 50.822 |
| "Snohomish" | 12.089 |
| "Pierce" | 8.026 |
| "Clark" | 5.97 |
| "Thurston" | 3.662 |
| … | … |
| "Penobscot" | 0.0 |
| "New York" | 0.0 |
| "Lane" | 0.0 |
| "Lee" | 0.0 |
| "Denton" | 0.0 |
Analizzando la combinazione tra contea e numero di auto vendute per contea, si nota come ci siano enormi differenze tra le contee. Si nota come le contee vicine a Seattle e Portland(si intendono le contee confinanti con lo stato dell'Oregon) abbiano un numero di auto vendute significativo rispetto alle altre contee.
A causa di questo, in seguito si andrà ad analizzare questo dato in base alla localizzazione geografica e non alla contea.
Per eseguire questa analisi, verifichiamo i dati che sono presenti nella colonna Vehicle Location.
Questi dati poi verranno usati per creare una mappa 3d in cui si vede dove sono state immatricolate le auto nello stato di Washington.
import re
import pandas as pd
data_point = (
data
.select(pl.col('Vehicle Location'))
)
#Si nota che è di tipo str
print(f'Tipologia del dato :{data_point[0]}')
#Creiamo una lista contenente tuple ('longitue','latitude'): (float, float)
coord_list = []
for row in data_point.rows():
if row[0] is not None:
numb = re.findall(r'-?\d+\.\d+|-?\d+', row[0])
coord = (float(numb[0]), float(numb[1]))
coord_list.append(coord)
#verifichiamo la lunghezza della lista
print(f'Lunghezza della lista coord_list: {len(coord_list)}')
#verifichiamo i primi 5 elementi di coord_list
print('Tipologia e dato di coord_list')
for i in range(5):
print(f'Tipo: {type(coord_list[i])} Dato:{coord_list[i]}')
Tipologia del dato :shape: (1, 1) ┌─────────────────────────────┐ │ Vehicle Location │ │ --- │ │ str │ ╞═════════════════════════════╡ │ POINT (-122.36498 47.72238) │ └─────────────────────────────┘ Lunghezza della lista coord_list: 216683 Tipologia e dato di coord_list Tipo: <class 'tuple'> Dato:(-122.36498, 47.72238) Tipo: <class 'tuple'> Dato:(-122.30207, 47.64085) Tipo: <class 'tuple'> Dato:(-122.54729, 47.42602) Tipo: <class 'tuple'> Dato:(-122.89166, 47.03956) Tipo: <class 'tuple'> Dato:(-122.87741, 47.05997)
(
data
.group_by('Make', 'Model Year')
.agg(
Vendita_per_marca = pl.col('Make').count()
)
)
| Make | Model Year | Vendita_per_marca |
|---|---|---|
| str | i64 | u32 |
| "JAGUAR" | 2019 | 117 |
| "CHRYSLER" | 2022 | 422 |
| "AUDI" | 2016 | 211 |
| "CHRYSLER" | 2023 | 1289 |
| "GENESIS" | 2024 | 112 |
| … | … | … |
| "SMART" | 2014 | 64 |
| "TOYOTA" | 2015 | 106 |
| "MAZDA" | 2025 | 72 |
| "TESLA" | 2013 | 723 |
| "HYUNDAI" | 2021 | 319 |
(
data
.group_by('Make', 'Electric Vehicle Type')
.agg(
Engine = pl.col('Electric Vehicle Type').count()
)
)
| Make | Electric Vehicle Type | Engine |
|---|---|---|
| str | str | u32 |
| "LUCID" | "BEV" | 317 |
| "JAGUAR" | "BEV" | 237 |
| "AUDI" | "PHEV" | 1709 |
| "GENESIS" | "BEV" | 297 |
| "DODGE" | "PHEV" | 701 |
| … | … | … |
| "KIA" | "BEV" | 7198 |
| "KIA" | "PHEV" | 2891 |
| "CHEVROLET" | "BEV" | 11053 |
| "TH!NK" | "BEV" | 5 |
| "ACURA" | "BEV" | 100 |
Per semplificare il layout del grafico a seguito di un'analisi è stato deciso di eliminare i modelli dei produttori che hanno venduto meno di 15 auto all'anno.
(
data
.group_by('Make', 'Model', 'Model Year')
.agg(
Total = pl.col('Model').count()
)
.filter(pl.col('Total') >= 15)
.sort([pl.col('Model Year'),pl.col('Total')], descending=False)
)
| Make | Model | Model Year | Total |
|---|---|---|---|
| str | str | i64 | u32 |
| "CHEVROLET" | "VOLT" | 2011 | 73 |
| "NISSAN" | "LEAF" | 2011 | 610 |
| "MITSUBISHI" | "I-MIEV" | 2012 | 40 |
| "TESLA" | "MODEL S" | 2012 | 128 |
| "TOYOTA" | "PRIUS PLUG-IN" | 2012 | 368 |
| … | … | … | … |
| "VOLVO" | "XC60" | 2025 | 95 |
| "BMW" | "IX" | 2025 | 112 |
| "NISSAN" | "LEAF" | 2025 | 139 |
| "BMW" | "X5" | 2025 | 189 |
| "RIVIAN" | "R1S" | 2025 | 412 |
Analisi esplorativa su prestazioni e costi.
Si vede inizialmente se tutti i dati del dataset dispongono delle informazioni su prestazioni e costi
data.describe()
| statistic | County | City | State | Model Year | Make | Model | Electric Vehicle Type | Electric Range | Base MSRP | Vehicle Location |
|---|---|---|---|---|---|---|---|---|---|---|
| str | str | str | str | f64 | str | str | str | f64 | f64 | str |
| "count" | "216683" | "216683" | "216683" | 216683.0 | "216683" | "216683" | "216683" | 216683.0 | 216683.0 | "216683" |
| "null_count" | "0" | "0" | "0" | 0.0 | "0" | "0" | "0" | 0.0 | 0.0 | "0" |
| "mean" | null | null | null | 2021.132636 | null | null | null | 49.391009 | 849.617044 | null |
| "std" | null | null | null | 2.974965 | null | null | null | 86.198317 | 7399.818083 | null |
| "min" | "Ada" | "Aberdeen" | "AK" | 2011.0 | "ACURA" | "330E" | "BEV" | 0.0 | 0.0 | "POINT (-100.50078 31.4168)" |
| "25%" | null | null | null | 2020.0 | null | null | null | 0.0 | 0.0 | null |
| "50%" | null | null | null | 2022.0 | null | null | null | 0.0 | 0.0 | null |
| "75%" | null | null | null | 2023.0 | null | null | null | 42.0 | 0.0 | null |
| "max" | "Yuba" | "Zillah" | "WY" | 2025.0 | "VOLVO" | "ZDX" | "PHEV" | 337.0 | 845000.0 | "POINT (-98.72277 29.44539)" |
Le colonne interessanti da analizzare sono:
data_ER = (
data
.filter(pl.col('Electric Range') > 0)
)
data_ER.describe()
| statistic | County | City | State | Model Year | Make | Model | Electric Vehicle Type | Electric Range | Base MSRP | Vehicle Location |
|---|---|---|---|---|---|---|---|---|---|---|
| str | str | str | str | f64 | str | str | str | f64 | f64 | str |
| "count" | "92587" | "92587" | "92587" | 92587.0 | "92587" | "92587" | "92587" | 92587.0 | 92587.0 | "92587" |
| "null_count" | "0" | "0" | "0" | 0.0 | "0" | "0" | "0" | 0.0 | 0.0 | "0" |
| "mean" | null | null | null | 2018.869453 | null | null | null | 115.590655 | 1988.373854 | null |
| "std" | null | null | null | 3.240357 | null | null | null | 98.67561 | 11219.89275 | null |
| "min" | "Adams" | "Aberdeen" | "AL" | 2011.0 | "ALFA ROMEO" | "330E" | "BEV" | 6.0 | 0.0 | "POINT (-100.50078 31.4168)" |
| "25%" | null | null | null | 2017.0 | null | null | null | 30.0 | 0.0 | null |
| "50%" | null | null | null | 2019.0 | null | null | null | 73.0 | 0.0 | null |
| "75%" | null | null | null | 2021.0 | null | null | null | 215.0 | 0.0 | null |
| "max" | "Yakima" | "Zillah" | "WI" | 2025.0 | "VOLVO" | "XM" | "PHEV" | 337.0 | 845000.0 | "POINT (-98.52212 29.61445)" |
Si nota che solo 92587 solo le righe che hanno il dato Electric Range maggiore di 0. Per eseguire l'analisi si consideranno solo i record che hanno dati positivi.
data_ER_EN = (
data
.select('Electric Vehicle Type', 'Electric Range')
.filter(pl.col('Electric Range') > 0)
)
data_ER_EN = (
data_ER_EN
.group_by('Electric Vehicle Type', 'Electric Range')
.agg(
Count = pl.col('Electric Range').count()
)
.sort(pl.col('Electric Range'))
)
data_ER_EN = data_ER_EN.with_columns(
pl.col('Electric Vehicle Type').replace({
'Plug-in Hybrid Electric Vehicle (PHEV)': 'PHEV',
'Battery Electric Vehicle (BEV)': 'BEV'
})
)
base = (
alt.Chart(data_ER_EN)
.encode(
x = ('Electric Range:Q'),
y = alt.Y('Count:Q'),
color= alt.Color('Electric Vehicle Type:N', scale=alt.Scale(scheme='oranges'))
)
)
base.mark_bar(cornerRadiusTopLeft=3, cornerRadiusTopRight=3)
Come facilmente ipotizzabile, le auto di tipologia PHEV hanno un'autonomia in solo elettrico minore rispetto alle auto BEV.
Si vuole vedere com'è distribuita l'autonomia delle auto prodotte in base al venditore.
data_range_prod = (
data
.select('Make', 'Electric Range', 'Electric Vehicle Type')
.filter(pl.col('Electric Range') > 0 )
)
data_range_prod = data_range_prod.with_columns(
pl.col('Electric Vehicle Type').replace({
'Plug-in Hybrid Electric Vehicle (PHEV)': 'PHEV',
'Battery Electric Vehicle (BEV)': 'BEV'
})
)
print(data_range_prod)
shape: (92_587, 3) ┌───────────┬────────────────┬───────────────────────┐ │ Make ┆ Electric Range ┆ Electric Vehicle Type │ │ --- ┆ --- ┆ --- │ │ str ┆ i64 ┆ str │ ╞═══════════╪════════════════╪═══════════════════════╡ │ NISSAN ┆ 75 ┆ BEV │ │ TESLA ┆ 270 ┆ BEV │ │ TOYOTA ┆ 25 ┆ PHEV │ │ FORD ┆ 19 ┆ PHEV │ │ TESLA ┆ 266 ┆ BEV │ │ … ┆ … ┆ … │ │ CHRYSLER ┆ 32 ┆ PHEV │ │ CHEVROLET ┆ 259 ┆ BEV │ │ CHEVROLET ┆ 38 ┆ PHEV │ │ KIA ┆ 33 ┆ PHEV │ │ CHEVROLET ┆ 38 ┆ PHEV │ └───────────┴────────────────┴───────────────────────┘
L'obiettivo è vedere tramite dei grafici a violino quante auto sono state vendute rispetto al'autonomia del motore elettrico per ogni marca.
Per farlo bisogna creare una tabelle in cui si contano il numero di auto che ha la stessa autonomia in elettrico per ogni produttore.
data_violin_graph=(
data_range_prod
.group_by('Make','Electric Range', )
.agg(
Count = pl.col('Electric Range').count()
)
.sort('Make')
)
print(data_violin_graph)
shape: (178, 3) ┌────────────┬────────────────┬───────┐ │ Make ┆ Electric Range ┆ Count │ │ --- ┆ --- ┆ --- │ │ str ┆ i64 ┆ u32 │ ╞════════════╪════════════════╪═══════╡ │ ALFA ROMEO ┆ 33 ┆ 90 │ │ AUDI ┆ 16 ┆ 567 │ │ AUDI ┆ 218 ┆ 73 │ │ AUDI ┆ 19 ┆ 11 │ │ AUDI ┆ 23 ┆ 510 │ │ … ┆ … ┆ … │ │ VOLVO ┆ 41 ┆ 162 │ │ VOLVO ┆ 38 ┆ 8 │ │ VOLVO ┆ 21 ┆ 30 │ │ VOLVO ┆ 19 ┆ 123 │ │ VOLVO ┆ 32 ┆ 608 │ └────────────┴────────────────┴───────┘
Per creare dei grafici con una buona quantità di dati eseguaiamo un controllo sul numero di dati disponibili. Per farlo verifichiamo quali sono le auto al di sotto di un certo range, scegliamo il 5%.
Creaiamo un grafico per vedere quali marchi eliminare dall'analisi successiva.
data_test_violin = (
data_range_prod
.group_by('Make')
.agg(
Count = pl.col('Make').count()/data_range_prod.height*100
)
.filter(pl.col('Count') < 5)
.sort(pl.col('Count'))
)
base = (
alt.Chart(data_test_violin)
.encode(
x='Count:Q',
y=alt.Y('Make:N', sort='-x'),
color=alt.Color('Count:Q', scale=alt.Scale(scheme='oranges'))
)
)
base.mark_bar()
#print(data_test_violin)
Scegliamo di eliminare i produttori che hanno venduto meno di MERCEDES-BENZ.
Creiamo una lista con i produttori di auto che rispettano il parametro di percentuale di auto vendute al di sopra del 0.5%.
make_list = (
data_range_prod
.group_by('Make')
.agg(
Count = pl.col('Make').count()/data_range_prod.height*100
)
.filter(pl.col('Count') > 0.5)
)
make_list = make_list['Make'].unique().sort().to_list()
make_list
['AUDI', 'BMW', 'CHEVROLET', 'CHRYSLER', 'DODGE', 'FIAT', 'FORD', 'HONDA', 'HYUNDAI', 'JEEP', 'KIA', 'MAZDA', 'MERCEDES-BENZ', 'MITSUBISHI', 'NISSAN', 'PORSCHE', 'TESLA', 'TOYOTA', 'VOLKSWAGEN', 'VOLVO']
Infine creaimo un grafico a violini, che ha l'obiettivo di vedere il numero di auto vendute per range dalla verie case automobilistiche.
Nell'asse delle X si vedranno il numero di auto vendute, nell'asse Y si vede il range, questo avviene per ogni produttore.
alt.data_transformers.disable_max_rows()
base = (
alt.Chart(data_violin_graph)
.encode(
alt.X('Count:Q')
.stack('center')
.impute(None)
.title(None)
.axis(labels = False, values = [0], grid = False, ticks = True),
alt.Y('Electric Range:Q'),
alt.Color('Make:N'),
alt.Column('Make:N')
.spacing(0)
.header(titleOrient='bottom', labelOrient='bottom', labelPadding=0)
)
.configure_view(
stroke = None
)
)
base.mark_area(orient='horizontal')
Con questa visualizzazione non si ha una visuale chiara dei dati. Proviamo con un tipo diverso di grafico.
Creiamo un grafico a linee in cui nell'asse delle X ci sono i chilometri di autonomia del motore elettrico, mentre nell'asse delle Y ci sono i produttori di auto.
bar = (
alt.Chart(data_violin_graph)
.mark_bar(cornerRadius=10, height=10)
.encode(
x = alt.X('min(Electric Range):Q').scale(domain=[-15, 350]).title('Electric Range'),
x2 = 'max(Electric Range):Q',
y = alt.Y('Make:N', sort = '-x').title(None),
)
)
text_min = (
alt.Chart(data_violin_graph)
.mark_text(align='right', dx = -5)
.encode(
x = 'min(Electric Range):Q',
y = alt.Y('Make:N'),
text = 'min(Electric Range):Q'
)
)
text_max = (
alt.Chart(data_violin_graph)
.mark_text(align='left', dx = 5)
.encode(
x = 'max(Electric Range):Q',
y = alt.Y('Make:N'),
text = 'max(Electric Range):Q'
)
)
(bar + text_min + text_max).properties(
title = alt.Title(text = 'Electric Range')
)
Il grafico ottenuto è interessante, ma per motivi di chiarezza si andranno a selezionare i marchi che hanno una differenza di autonomia di motore elettrico (modello con minima autonomia e modello con massima autonomia superiore a 5).
Successivamente nella pagina streamlit, questo verrà affiancato da una tabella e da una spiegazione così da non perdere i dati.
filtered_data = (
data_violin_graph
.group_by("Make")
.agg([
pl.col("Electric Range").max().alias("max_Electric_Range"),
pl.col("Electric Range").min().alias("min_Electric_Range"),
])
.filter((pl.col("max_Electric_Range") - pl.col("min_Electric_Range")) < 5)
)
filter_list = filtered_data['Make'].unique().sort().to_list()
filter_list
data_violin_graph = (
data_violin_graph
.filter(pl.col('Make').is_in(list(set(make_list) - set(filter_list))))
)
bar = (
alt.Chart(data_violin_graph)
.mark_bar(cornerRadius=10, height=10)
.encode(
x = alt.X('min(Electric Range):Q').scale(domain=[-15, 350]).title('Electric Range'),
x2 = 'max(Electric Range):Q',
y = alt.Y('Make:N', sort = '-x').title(None),
color=alt.value('orange')
)
)
text_min = (
alt.Chart(data_violin_graph)
.mark_text(align='right', dx = -5)
.encode(
x = 'min(Electric Range):Q',
y = alt.Y('Make:N'),
text = 'min(Electric Range):Q'
)
)
text_max = (
alt.Chart(data_violin_graph)
.mark_text(align='left', dx = 5)
.encode(
x = 'max(Electric Range):Q',
y = alt.Y('Make:N'),
text = 'max(Electric Range):Q'
)
)
(bar + text_min + text_max).properties(
title = alt.Title(text = 'Electric Range')
)
Eseguiamo un analisi per quanto riguarda la tipologia del motore. Questa successivamente sarà molto utile perchè ci servirà per creare la visualizzazione sul sito streamlit.
L'obiettivo è quello di suddividere le auto per tipologia di motore, e verificare la media dell'autonomia suddivisa per categoria.
#Numero dati con cui eseguiamo l'analisi
data_label = (
data
.select('Electric Vehicle Type', 'Electric Range')
.filter(pl.col('Electric Range') > 0)
)
data_label = data_label.with_columns(
pl.col('Electric Vehicle Type').replace({
'Plug-in Hybrid Electric Vehicle (PHEV)': 'PHEV',
'Battery Electric Vehicle (BEV)': 'BEV'
})
)
data_label.height
data_label
data_mean = (
data_label
.mean()
.select('Electric Range').item()
)
round(data_mean, 2)
115.59
Ora dividiamo per tipologia di motore
data_label
| Electric Vehicle Type | Electric Range |
|---|---|
| str | i64 |
| "BEV" | 75 |
| "BEV" | 270 |
| "PHEV" | 25 |
| "PHEV" | 19 |
| "BEV" | 266 |
| … | … |
| "PHEV" | 32 |
| "BEV" | 259 |
| "PHEV" | 38 |
| "PHEV" | 33 |
| "PHEV" | 38 |
data_mean_by_engine = (
data_label
.group_by('Electric Vehicle Type')
.agg(
Count = pl.col('Electric Range').count(),
Mean = pl.col('Electric Range').mean()
)
.with_columns(
pl.col('Mean').round(2).alias('Mean')
)
)
data_mean_by_engine
value = data_mean_by_engine.row(0)[1]
print(data_mean_by_engine)
shape: (2, 3) ┌───────────────────────┬───────┬────────┐ │ Electric Vehicle Type ┆ Count ┆ Mean │ │ --- ┆ --- ┆ --- │ │ str ┆ u32 ┆ f64 │ ╞═══════════════════════╪═══════╪════════╡ │ PHEV ┆ 45561 ┆ 31.03 │ │ BEV ┆ 47026 ┆ 197.52 │ └───────────────────────┴───────┴────────┘
Risulta interessante ora vedere come sono distribuite le vendite in base all'autonomia. Inizialmente vediamo come si distribuisce a livello di campione, successivamente si va a distinguere per produttore.
data_strip_plot = (
data
.select('Make', 'Electric Range', 'Electric Vehicle Type')
.filter(pl.col('Electric Range') > 0 )
)
data_strip_plot = data_strip_plot.with_columns(
pl.col('Electric Vehicle Type').replace({
'Plug-in Hybrid Electric Vehicle (PHEV)': 'PHEV',
'Battery Electric Vehicle (BEV)': 'BEV'
})
)
gaussian_jitter = alt.Chart(data_strip_plot).mark_circle(size = 8).encode(
y = 'Electric Vehicle Type:N',
x = 'Electric Range:Q',
yOffset='jitter:Q',
color=alt.Color('Electric Vehicle Type:N', scale=alt.Scale(scheme='oranges')).legend(None)
).transform_calculate(
jitter = "sqrt(-2*log(random()))*cos(2*PI*random())"
)
uniform_jitter = gaussian_jitter.transform_calculate(
jitter='random()'
).encode(
alt.Y('Electric Vehicle Type:N').axis(None)
)
(gaussian_jitter | uniform_jitter).resolve_scale(yOffset='independent')
Ora facciamo lo stesso grafico, per le marche che fanno parte del dataset del grafico in cui si vede la massima e la minima autonomia dei modelli venduti.
def make_strip_plot(data):
data_range_prod = (
data
.select('Make', 'Electric Range', 'Electric Vehicle Type')
.filter(pl.col('Electric Range') > 0 )
)
data_range_prod = data_range_prod.with_columns(
pl.col('Electric Vehicle Type').replace({
'Plug-in Hybrid Electric Vehicle (PHEV)': 'PHEV',
'Battery Electric Vehicle (BEV)': 'BEV'
})
)
range_graph = (
data_range_prod
.group_by('Make','Electric Range', )
.agg(
Count = pl.col('Electric Range').count()
)
.sort('Make')
)
make_list = (
data_range_prod
.group_by('Make')
.agg(
Count = pl.col('Make').count()/data_range_prod.height*100
)
.filter(pl.col('Count') > 0.5)
)
make_list = make_list['Make'].unique().sort().to_list()
filtered_data = (
range_graph
.group_by("Make")
.agg([
pl.col("Electric Range").max().alias("Max Range"),
pl.col("Electric Range").min().alias("Min Range"),
])
)
filtered_data = (
filtered_data
.filter((pl.col("Max Range") - pl.col("Min Range")) < 5)
)
filter_list = filtered_data['Make'].unique().sort().to_list()
return list(set(make_list) - set(filter_list))
test = make_strip_plot(data)
data_jitter_plot = (
data
.select('Make', 'Electric Range', )
.filter(pl.col('Electric Range') > 0)
.filter(pl.col('Make').is_in(test))
)
gaussian_jitter = alt.Chart(data_jitter_plot).mark_circle(size = 8).encode(
y = 'Make:N',
x = 'Electric Range:Q',
yOffset='jitter:Q',
color=alt.Color('Make:N', scale=alt.Scale(scheme='oranges')).legend(None)
).transform_calculate(
jitter = "sqrt(-2*log(random()))*cos(2*PI*random())"
)
(gaussian_jitter).resolve_scale(yOffset='independent')
Visto così non risulta molto utile, si può optare per vedere questo grafico suddiviso per marchio
test_list = ['TESLA', 'HYUNDAI', 'KIA', 'TOYOTA']
test_grafico = (
data
.select('Make', 'Electric Range', 'Electric Vehicle Type')
.filter(pl.col('Make').is_in(test_list))
.filter(pl.col('Electric Range') > 0)
)
gaussian_jitter = alt.Chart(test_grafico).mark_circle(size = 8).encode(
y = 'Make:N',
x = 'Electric Range:Q',
yOffset='jitter:Q',
color=alt.Color('Electric Vehicle Type:N', scale=alt.Scale(scheme='purpleorange'))
).transform_calculate(
jitter = "sqrt(-2*log(random()))*cos(2*PI*random())"
)
(gaussian_jitter).resolve_scale(yOffset='independent')
Analisi sui prezzi delle auto.
data_prezzi = (
data.filter(pl.col('Base MSRP') > 0)
)
data_prezzi.describe()
| statistic | County | City | State | Model Year | Make | Model | Electric Vehicle Type | Electric Range | Base MSRP | Vehicle Location |
|---|---|---|---|---|---|---|---|---|---|---|
| str | str | str | str | f64 | str | str | str | f64 | f64 | str |
| "count" | "3259" | "3259" | "3259" | 3259.0 | "3259" | "3259" | "3259" | 3259.0 | 3259.0 | "3259" |
| "null_count" | "0" | "0" | "0" | 0.0 | "0" | "0" | "0" | 0.0 | 0.0 | "0" |
| "mean" | null | null | null | 2015.717091 | null | null | null | 119.622277 | 56488.975146 | null |
| "std" | null | null | null | 2.39341 | null | null | null | 89.979303 | 22311.569047 | null |
| "min" | "Adams" | "Aberdeen" | "CA" | 2011.0 | "BMW" | "330E" | "BEV" | 12.0 | 31950.0 | "POINT (-115.20278 36.29143)" |
| "25%" | null | null | null | 2013.0 | null | null | null | 17.0 | 39995.0 | null |
| "50%" | null | null | null | 2016.0 | null | null | null | 93.0 | 59900.0 | null |
| "75%" | null | null | null | 2018.0 | null | null | null | 208.0 | 69900.0 | null |
| "max" | "Yakima" | "Zillah" | "WA" | 2020.0 | "VOLVO" | "XC90" | "PHEV" | 265.0 | 845000.0 | "POINT (-97.85322 30.57955)" |
La prima cosa che si nota è che il numero di dati presenti cala in modo molto significativo. Vediamo come cambia la media della base di vendita anno per anno.
data_year_price = (
data_prezzi
.group_by('Model Year')
.agg(
Mean = pl.col('Base MSRP').mean(),
Count = pl.col('Model Year').count()
)
.filter(pl.col('Count')>100)
.sort('Model Year')
)
data_year_price
| Model Year | Mean | Count |
|---|---|---|
| i64 | f64 | u32 |
| 2012 | 63508.571429 | 140 |
| 2013 | 69900.0 | 723 |
| 2014 | 69900.0 | 617 |
| 2016 | 32203.369272 | 371 |
| 2017 | 39228.54 | 250 |
| 2018 | 53920.394127 | 647 |
| 2019 | 44561.356994 | 479 |
alt.Chart(data_year_price).mark_line(point={'color': 'orange', 'size': 30}, color='orange').encode(
x='Model Year:O',
y='Mean:Q',
).properties(
width = 500
)
Risulta interessante vedere con uno scatter plot se la tipologia del motore incide sul prezzo.
A seguito di una piccola analisi, risulta molto utile eliminare i dati che hanno una valore di Base MSRP sopra i 120k dollari, altrimenti porta ad avere una visualizzazione dei dati non chiara.
data_scatter_price_range = (
data_prezzi
.select('Base MSRP', 'Electric Range', 'Electric Vehicle Type')
.filter(pl.col('Electric Range') > 0)
.filter(pl.col('Base MSRP') < 120000)
)
data_scatter_price_range
| Base MSRP | Electric Range | Electric Vehicle Type |
|---|---|---|
| i64 | i64 | str |
| 44100 | 14 | "PHEV" |
| 31950 | 93 | "BEV" |
| 69900 | 208 | "BEV" |
| 31950 | 93 | "BEV" |
| 59900 | 265 | "BEV" |
| … | … | … |
| 69900 | 208 | "BEV" |
| 69900 | 208 | "BEV" |
| 45600 | 14 | "PHEV" |
| 109000 | 245 | "BEV" |
| 39995 | 32 | "PHEV" |
alt.Chart(data_scatter_price_range).mark_point().encode(
x='Electric Range:Q',
y='Base MSRP:Q',
color=alt.Color('Electric Vehicle Type:N', scale=alt.Scale(scheme='purpleorange')),
size= 'count()'
)
In questa parte aggiungo del codice per vedere se si riescono ad aggiungere altre cose interessanti alla dashboard.
Ora si vuole vedere la percentuale di auto per tipologia di motore che un determinare produttore ha venduto.
data_HONDA = (
data
.filter(
pl.col('Make') == 'HONDA'
)
)
test = (
data_HONDA
#.select('Make')
.group_by('Electric Vehicle Type')
.agg(
Tot_Engine = ((pl.col('Electric Vehicle Type').count())/data_HONDA.height).round(2)
)
.sort(pl.col('Electric Vehicle Type'))
)
test
| Electric Vehicle Type | Tot_Engine |
|---|---|
| str | f64 |
| "BEV" | 0.44 |
| "PHEV" | 0.56 |
Questo risultato lo aggiungo alla label del mini report.
Ora si vuole contare il numero di modelli venduti da un produttore suddivisi per tipologia di motore.
model_engine =(
data_HONDA
.select('Model', 'Electric Vehicle Type').unique()
)
(
model_engine
.group_by('Electric Vehicle Type')
.agg(
tot = pl.col('Electric Vehicle Type').count()
)
.sort(pl.col('Electric Vehicle Type'))
)
| Electric Vehicle Type | tot |
|---|---|
| str | u32 |
| "BEV" | 2 |
| "PHEV" | 2 |
Cerchiamo prezzo medio delle auto, poi prezzo medio auto per le tipologie di motori
data_mean_price = (
data
.filter(pl.col('Base MSRP') > 0)
.select(pl.col('Base MSRP').mean().round(2))
)
print(data_mean_price.row(0)[0])
data_mean_price_engine = (
data
.filter(pl.col('Base MSRP') > 0)
.group_by('Electric Vehicle Type')
.agg(
Mean = pl.col('Base MSRP').mean().round(2)
)
.sort(pl.col('Electric Vehicle Type'))
)
data_mean_price_engine
56488.98
| Electric Vehicle Type | Mean |
|---|---|
| str | f64 |
| "BEV" | 58854.51 |
| "PHEV" | 52477.93 |